#!/usr/bin/env python3
"""
ONVIF摄像头管理系统核心功能示例实现
"""

import asyncio
import socket
import struct
import uuid
import xml.etree.ElementTree as ET
import ping3
import time
import statistics
from datetime import datetime
from typing import List, Dict, Optional
from dataclasses import dataclass
from enum import Enum

class DeviceStatus(Enum):
    ONLINE = "online"
    OFFLINE = "offline"
    NETWORK_ISSUE = "network_issue"
    SERVICE_ISSUE = "service_issue"
    UNKNOWN = "unknown"

@dataclass
class DeviceInfo:
    device_id: str
    ip_address: str
    endpoint_url: str
    manufacturer: str = ""
    model: str = ""
    status: DeviceStatus = DeviceStatus.UNKNOWN
    last_seen: Optional[datetime] = None

@dataclass
class NetworkMetrics:
    latency_ms: float
    packet_loss_rate: float
    jitter_ms: float
    timestamp: datetime

class WSDiscovery:
    """WS-Discovery协议实现"""
    
    def __init__(self):
        self.multicast_addr = '239.255.255.250'
        self.multicast_port = 3702
        self.buffer_size = 4096
        
    def create_probe_message(self) -> bytes:
        """创建WS-Discovery Probe消息"""
        message_id = str(uuid.uuid4())
        probe_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
               xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
               xmlns:wsd="http://schemas.xmlsoap.org/ws/2005/04/discovery"
               xmlns:tdn="http://www.onvif.org/ver10/network/wsdl">
    <soap:Header>
        <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>
        <wsa:MessageID>urn:uuid:{message_id}</wsa:MessageID>
        <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>
    </soap:Header>
    <soap:Body>
        <wsd:Probe>
            <wsd:Types>tdn:NetworkVideoTransmitter</wsd:Types>
        </wsd:Probe>
    </soap:Body>
</soap:Envelope>'''
        return probe_xml.encode('utf-8')
    
    def discover_devices(self, timeout: int = 5) -> List[DeviceInfo]:
        """发现ONVIF设备"""
        devices = []
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(timeout)
        
        try:
            # 发送Probe消息
            probe_msg = self.create_probe_message()
            sock.sendto(probe_msg, (self.multicast_addr, self.multicast_port))
            print(f"发送WS-Discovery Probe消息到 {self.multicast_addr}:{self.multicast_port}")
            
            # 接收响应
            start_time = datetime.now()
            while (datetime.now() - start_time).total_seconds() < timeout:
                try:
                    data, addr = sock.recvfrom(self.buffer_size)
                    device_info = self.parse_probe_match(data, addr[0])
                    if device_info:
                        devices.append(device_info)
                        print(f"发现设备: {device_info.ip_address} - {device_info.endpoint_url}")
                except socket.timeout:
                    break
                except Exception as e:
                    print(f"接收响应时出错: {e}")
                    
        except Exception as e:
            print(f"发现设备时出错: {e}")
        finally:
            sock.close()
            
        return devices
    
    def parse_probe_match(self, data: bytes, ip_address: str) -> Optional[DeviceInfo]:
        """解析ProbeMatch响应"""
        try:
            root = ET.fromstring(data.decode('utf-8'))
            
            # 查找XAddrs元素（设备端点）
            namespaces = {
                'soap': 'http://www.w3.org/2003/05/soap-envelope',
                'wsd': 'http://schemas.xmlsoap.org/ws/2005/04/discovery'
            }
            
            xaddrs_elem = root.find('.//wsd:XAddrs', namespaces)
            if xaddrs_elem is not None and xaddrs_elem.text:
                endpoint_url = xaddrs_elem.text.strip()
                device_id = f"onvif_{ip_address}_{int(time.time())}"
                
                return DeviceInfo(
                    device_id=device_id,
                    ip_address=ip_address,
                    endpoint_url=endpoint_url,
                    status=DeviceStatus.UNKNOWN,
                    last_seen=datetime.now()
                )
                
        except Exception as e:
            print(f"解析ProbeMatch响应失败: {e}")
            
        return None

class NetworkMonitor:
    """网络监控类"""
    
    def __init__(self, ping_timeout: int = 5, ping_count: int = 5):
        self.ping_timeout = ping_timeout
        self.ping_count = ping_count
    
    def ping_device(self, ip_address: str) -> NetworkMetrics:
        """Ping设备并返回网络指标"""
        results = []
        lost_packets = 0
        
        print(f"正在ping {ip_address}...")
        
        for i in range(self.ping_count):
            try:
                response_time = ping3.ping(ip_address, timeout=self.ping_timeout)
                if response_time is not None:
                    results.append(response_time * 1000)  # 转换为毫秒
                    print(f"  ping {i+1}: {response_time*1000:.2f}ms")
                else:
                    lost_packets += 1
                    print(f"  ping {i+1}: 超时")
            except Exception as e:
                lost_packets += 1
                print(f"  ping {i+1}: 错误 - {e}")
        
        # 计算指标
        if results:
            avg_latency = statistics.mean(results)
            jitter = statistics.stdev(results) if len(results) > 1 else 0
        else:
            avg_latency = 0
            jitter = 0
        
        packet_loss_rate = (lost_packets / self.ping_count) * 100
        
        return NetworkMetrics(
            latency_ms=avg_latency,
            packet_loss_rate=packet_loss_rate,
            jitter_ms=jitter,
            timestamp=datetime.now()
        )
    
    def check_tcp_port(self, ip_address: str, port: int, timeout: int = 5) -> bool:
        """检查TCP端口连通性"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            result = sock.connect_ex((ip_address, port))
            sock.close()
            return result == 0
        except Exception:
            return False

class DeviceManager:
    """设备管理类"""
    
    def __init__(self):
        self.devices: Dict[str, DeviceInfo] = {}
        self.network_monitor = NetworkMonitor()
        self.discovery = WSDiscovery()
    
    def discover_devices(self) -> List[DeviceInfo]:
        """发现设备"""
        print("开始发现ONVIF设备...")
        devices = self.discovery.discover_devices()
        
        for device in devices:
            self.devices[device.device_id] = device
            
        print(f"发现 {len(devices)} 个设备")
        return devices
    
    def monitor_device(self, device_id: str) -> Optional[Dict]:
        """监控单个设备"""
        if device_id not in self.devices:
            return None
            
        device = self.devices[device_id]
        print(f"\n监控设备: {device.ip_address}")
        
        # 网络连通性检测
        metrics = self.network_monitor.ping_device(device.ip_address)
        
        # TCP端口检测
        common_ports = [80, 8080, 554, 8554]
        open_ports = []
        for port in common_ports:
            if self.network_monitor.check_tcp_port(device.ip_address, port):
                open_ports.append(port)
        
        # 判断设备状态
        if metrics.packet_loss_rate == 100:
            device.status = DeviceStatus.OFFLINE
        elif metrics.packet_loss_rate > 50:
            device.status = DeviceStatus.NETWORK_ISSUE
        elif not open_ports:
            device.status = DeviceStatus.SERVICE_ISSUE
        else:
            device.status = DeviceStatus.ONLINE
            device.last_seen = datetime.now()
        
        status_info = {
            'device_id': device_id,
            'ip_address': device.ip_address,
            'status': device.status.value,
            'last_seen': device.last_seen.isoformat() if device.last_seen else None,
            'network_metrics': {
                'latency_ms': metrics.latency_ms,
                'packet_loss_rate': metrics.packet_loss_rate,
                'jitter_ms': metrics.jitter_ms,
                'timestamp': metrics.timestamp.isoformat()
            },
            'open_ports': open_ports
        }
        
        print(f"设备状态: {device.status.value}")
        print(f"延迟: {metrics.latency_ms:.2f}ms")
        print(f"丢包率: {metrics.packet_loss_rate:.1f}%")
        print(f"抖动: {metrics.jitter_ms:.2f}ms")
        print(f"开放端口: {open_ports}")
        
        return status_info
    
    def monitor_all_devices(self) -> List[Dict]:
        """监控所有设备"""
        results = []
        for device_id in self.devices:
            result = self.monitor_device(device_id)
            if result:
                results.append(result)
        return results
    
    def get_device_list(self) -> List[Dict]:
        """获取设备列表"""
        return [
            {
                'device_id': device.device_id,
                'ip_address': device.ip_address,
                'endpoint_url': device.endpoint_url,
                'manufacturer': device.manufacturer,
                'model': device.model,
                'status': device.status.value,
                'last_seen': device.last_seen.isoformat() if device.last_seen else None
            }
            for device in self.devices.values()
        ]

async def main():
    """主函数示例"""
    print("ONVIF摄像头管理系统示例")
    print("=" * 50)
    
    # 创建设备管理器
    manager = DeviceManager()
    
    # 发现设备
    devices = manager.discover_devices()
    
    if not devices:
        print("未发现任何ONVIF设备")
        print("请确保:")
        print("1. 网络中有ONVIF设备")
        print("2. 设备已启用ONVIF服务")
        print("3. 防火墙允许多播通信")
        return
    
    # 显示发现的设备
    print("\n发现的设备:")
    device_list = manager.get_device_list()
    for i, device in enumerate(device_list, 1):
        print(f"{i}. {device['ip_address']} - {device['endpoint_url']}")
    
    # 监控设备
    print("\n开始监控设备...")
    while True:
        try:
            results = manager.monitor_all_devices()
            
            # 显示监控结果摘要
            online_count = sum(1 for r in results if r['status'] == 'online')
            offline_count = sum(1 for r in results if r['status'] == 'offline')
            
            print(f"\n监控摘要 ({datetime.now().strftime('%H:%M:%S')}):")
            print(f"在线设备: {online_count}")
            print(f"离线设备: {offline_count}")
            print(f"总设备数: {len(results)}")
            
            # 等待下次监控
            await asyncio.sleep(30)  # 30秒间隔
            
        except KeyboardInterrupt:
            print("\n停止监控")
            break
        except Exception as e:
            print(f"监控过程中出错: {e}")
            await asyncio.sleep(5)

if __name__ == "__main__":
    # 运行示例
    asyncio.run(main())
